use anyhow::Context;
use sea_orm::{
    ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, DbErr, DeleteResult,
    EntityTrait, JoinType, PaginatorTrait, QueryFilter, QuerySelect, RelationTrait, TryIntoModel,
};
use sky_common::constant::status;
use sky_pojo::{
    dto::setmeal::SetMealQueryDTO,
    entities::{
        dish,
        setmeal::{self, Model},
        setmeal_dish,
    },
    vo::{dish::DishItemVO, PageResult},
};

pub async fn count_by_category(db: DatabaseConnection, category_id: i64) -> Result<u64, String> {
    let result = setmeal::Entity::find()
        .filter(setmeal::Column::CategoryId.eq(category_id))
        .count(&db)
        .await;
    match result {
        Ok(count) => Ok(count),
        Err(err) => Err(err.to_string()),
    }
}

pub async fn create(
    db: DatabaseConnection,
    model: setmeal::ActiveModel,
) -> Result<setmeal::Model, anyhow::Error> {
    let result = setmeal::Entity::insert(model)
        .exec_with_returning(&db)
        .await
        .context("Failed to create dish")?;
    Ok(result)
}

pub async fn insert_setmeal_dishes(
    db: DatabaseConnection,
    setmeal_dishes: Vec<setmeal_dish::Model>,
    dish_id: i64,
) -> Result<(), anyhow::Error> {
    for sd in setmeal_dishes {
        let name = sd.name.clone();
        let model = setmeal_dish::ActiveModel {
            setmeal_id: ActiveValue::set(sd.setmeal_id),
            dish_id: ActiveValue::set(Some(dish_id)),
            name: ActiveValue::set(sd.name),
            price: ActiveValue::set(sd.price),
            copies: ActiveValue::set(sd.copies),
            ..Default::default()
        };

        let _model = setmeal_dish::Entity::insert(model)
            .exec_with_returning(&db)
            .await
            .context(format!(
                "Failed to insert flavor with dish_id: {:?} and name: {:#?}",
                sd.dish_id, name
            ))?;
    }
    Ok(())
}

pub async fn modify_by_id(
    db: DatabaseConnection,
    dish_id: i64,
    model: setmeal::ActiveModel,
) -> Result<setmeal::Model, DbErr> {
    setmeal::Entity::update(model)
        .filter(setmeal::Column::Id.eq(dish_id))
        .exec(&db)
        .await
}

pub async fn dish_modify_branches(
    db: DatabaseConnection,
    dish_id: i64,
    setmeal_dishes: Vec<setmeal_dish::Model>,
) -> Result<Vec<setmeal_dish::Model>, DbErr> {
    let mut updated_sds = Vec::new();

    for sd in setmeal_dishes {
        let model = setmeal_dish::ActiveModel {
            dish_id: ActiveValue::Set(sd.dish_id),
            name: ActiveValue::Set(sd.name),
            setmeal_id: ActiveValue::set(sd.setmeal_id),
            price: ActiveValue::set(sd.price),
            copies: ActiveValue::set(sd.copies),
            ..Default::default()
        };
        let updated = setmeal_dish::Entity::update(model)
            .filter(setmeal_dish::Column::DishId.eq(dish_id))
            .exec(&db)
            .await?;
        updated_sds.push(updated);
    }
    Ok(updated_sds)
}

pub async fn query_by_id(db: DatabaseConnection, id: i64) -> Result<setmeal::Model, DbErr> {
    let result = setmeal::Entity::find()
        .filter(setmeal::Column::Id.eq(id))
        .filter(setmeal::Column::Name.is_not_null())
        .filter(setmeal::Column::Status.eq(status::ENABLE))
        .one(&db)
        .await;

    match result {
        Ok(Some(model)) => Ok(model),
        Ok(None) => Err(DbErr::RecordNotFound(format!("Model not found"))),
        Err(e) => Err(e),
    }
}

pub async fn query_by_setmeal_dishes(
    db: DatabaseConnection,
    dish_id: i64,
) -> Result<Vec<setmeal_dish::Model>, DbErr> {
    setmeal_dish::Entity::find()
        .filter(setmeal_dish::Column::DishId.eq(dish_id))
        .all(&db)
        .await
}

pub async fn query(
    db: DatabaseConnection,
    dto: SetMealQueryDTO,
) -> Result<PageResult<setmeal::Model>, DbErr> {
    let mut stmt = setmeal::Entity::find();

    if let Some(name) = dto.name {
        if !name.is_empty() {
            stmt = stmt.filter(setmeal::Column::Name.contains(&name));
        }
    }

    // 条件过滤
    if let Some(category_id) = dto.category_id {
        stmt.clone().filter(setmeal::Column::Id.eq(category_id));
    }

    if let Some(status) = dto.status {
        stmt.clone().filter(setmeal::Column::Id.eq(status));
    }

    // 总记录数
    let total: u64 = stmt.clone().count(&db).await?;

    // 分页数据
    let dish_vec = stmt
        .paginate(&db, dto.page_size.try_into().unwrap())
        .fetch_page((dto.page - 1).try_into().unwrap())
        .await?;

    Ok(PageResult {
        total,
        records: dish_vec,
    })
}

pub async fn delete_by_id(db: DatabaseConnection, id: i64) -> Result<DeleteResult, DbErr> {
    let res = setmeal::Entity::delete_by_id(id).exec(&db).await;
    res
}

pub async fn delete_by_setmeal_id(db: DatabaseConnection, id: i64) -> Result<DeleteResult, DbErr> {
    setmeal_dish::Entity::delete_many()
        .filter(setmeal_dish::Column::DishId.eq(id))
        .exec(&db)
        .await
}

// for user
pub async fn list(
    db: DatabaseConnection,
    mut setmeal: setmeal::ActiveModel,
) -> Result<Vec<setmeal::Model>, DbErr> {
    let mut stmt = setmeal::Entity::find();

    let name = setmeal.name.take();
    let category_id = setmeal.category_id.take().unwrap();
    let status = setmeal.status.take().unwrap().unwrap();

    match name {
        Some(name) => {
            if !name.is_empty() {
                stmt.clone().filter(setmeal::Column::Name.contains(name));
            }
        }
        None => {}
    }

    stmt = stmt
        .filter(setmeal::Column::CategoryId.eq(category_id))
        .filter(setmeal::Column::Status.eq(status));

    stmt.all(&db).await
}
// see https://www.sea-ql.org/SeaORM/docs/relation/custom-join-condition/
// 使用 .columns 需要在相同Entity (不然编译器过不了
pub async fn get_dish_item_by_setmeal_id(
    db: &DatabaseConnection,
    setmeal_id: i64,
) -> Result<Vec<DishItemVO>, DbErr> {
    setmeal_dish::Entity::find()
        .select_only()
        .column(setmeal_dish::Column::Name)
        .column(setmeal_dish::Column::Copies)
        .column(dish::Column::Image)
        .column(dish::Column::Description)
        .join(JoinType::LeftJoin, setmeal_dish::Relation::Dish.def())
        .filter(setmeal_dish::Column::SetmealId.eq(setmeal_id))
        .into_model::<DishItemVO>()
        .all(db)
        .await
}
